// build:20240411
// Communication with an MQTT server is achieved using the MQTT protocol, or communication with other MQTT clients is achieved through an MQTT broker.
// Dependencies: dxMap, dxLogger, dxDriver, dxCommon, dxEventBus, dxNet
import { mqttClass } from './libvbar-m-dxmqtt.so'
import std from './dxStd.js'
import dxMap from './dxMap.js'
import dxCommon from './dxCommon.js'
import bus from './dxEventBus.js'
const map = dxMap.get("default")
const mqttObj = new mqttClass();
const mqtt = {}
/**
 * Initialize MQTT-related properties and create a connection. Use the dxMQTT component or the simplified function dxMQTT.run in the worker.
 * @param {string} mqttAddr MQTT service address, required, must begin with tcp://, format is tcp://ip:port
 * @param {string} clientId Client ID (required). Please use different client IDs for different devices.
 * @param {string} username Optional, MQTT username
 * @param {string} password Optional field: MQTT password
 * @param {string} prefix This field is not required and defaults to an empty string. This means that a prefix will be automatically added before the topic.
 * @param {number} qos 0, 1, 2 are optional; the default is 1. 
        0 indicates the message will be sent at most once and then discarded; 
        1 indicates the message will be sent at least once, guaranteeing receipt by the receiver, but there is a possibility of the receiver receiving duplicate messages; 
        2 indicates the message will be sent successfully and only once, resulting in high resource overhead.
 * @param {string} willTopic Optional. This is the subject of the will message. When communication through the broker is interrupted, an MQTT will message will be automatically triggered.
 * @param {string} willMessage Optional. This field contains the will's content. When communication via the broker is interrupted, an MQTT will message will be automatically triggered; this is the content of the will message.
 * @param {string} id Handle ID, optional (a unique ID is required if initializing multiple instances).
 */
mqtt.init = function (mqttAddr, clientId, username, password, prefix = "", qos = 1, willTopic, willMessage, id) {

    if (mqttAddr === undefined || mqttAddr.length === 0) {
        throw new Error("dxMqtt.init: 'mqttAddr' parameter should not be null or empty")
    }
    if (clientId === undefined || clientId.length === 0) {
        throw new Error("dxMqtt.init: 'clientId' parameter should not be null or empty")
    }
    let pointer = mqttObj.init(mqttAddr, clientId, username, password, prefix, qos, willTopic, willMessage);
    if (pointer === undefined || pointer === null) {
        throw new Error("dxMqtt.init: mqtt init failed")
    }

    dxCommon.handleId("mqtt", id, pointer)
}

/**
 * Reconnecting, for example, if the network suddenly drops after a successful connection, there's no need to re-init; simply reconnect.
 * @param {string} willTopic Optional. This is the subject of the will message. When communication through the broker is interrupted, an MQTT will message will be automatically triggered.
 * @param {string} willMessage Optional. This field contains the will's content. When communication via the broker is interrupted, an MQTT will message will be automatically triggered; this is the content of the will message.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 */
mqtt.reconnect = function (willTopic, willMessage, id) {
    let pointer = dxCommon.handleId("mqtt", id)
    return mqttObj.recreate(pointer, willTopic, willMessage);
}

/**
 * Subscribe to multiple topics
 * @param {array} topics Required: An array of topics to subscribe to; you can subscribe to multiple topics simultaneously. 
 * @param {number} qos Optional, default is 1. 
        0 indicates the message will be sent at most once and then discarded; 
        1 indicates the message will be sent at least once, guaranteeing receipt by the recipient, but there is a possibility of the recipient receiving duplicate messages; 
        2 indicates the message will be sent successfully and only once, resulting in high resource overhead.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns 
 */
mqtt.subscribes = function (topics, qos, id) {
    if (topics === undefined || topics.length === 0) {
        throw new Error("dxMqtt.subscribes: 'topics' parameter should not be null or empty")
    }

    if (qos === undefined) {
        qos = 1
    }
    let pointer = dxCommon.handleId("mqtt", id)
    return mqttObj.subscribes(pointer, topics, qos);
}

/**
 * To check if the MQTT connection is established, if the network connection drops after a successful connection, the connection will also be terminated.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns false indicates failure; true indicates success.
 */
mqtt.isConnected = function (id) {
    let pointer = dxCommon.handleId("mqtt", id)
    return mqttObj.isConnected(pointer);
}

/**
 * Query MQTT configuration
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns MQTT configuration
 */
mqtt.getConfig = function (id) {
    let pointer = dxCommon.handleId("mqtt", id)
    return mqttObj.getConfig(pointer);
}

/**
 * MQTT configuration update
 * @param {object} options Most configuration parameters can use the default values.
 *      @param {string} options.mqttAddr MQTT service address, required, must begin with tcp://, format is tcp://ip:port
 *      @param {string} options.clientId Client ID (required). Please use different client IDs for different devices.
 *      @param {string} options.userName Optional, MQTT username
 *      @param {string} options.password Optional field: MQTT password
 *      @param {string} options.prefix This field is not required and defaults to an empty string. This means that a prefix will be automatically added before the topic.
 *      @param {number} options.qos 0, 1, 2 are optional; the default is 1. 
            0 indicates the message will be sent at most once and then discarded; 
            1 indicates the message will be sent at least once, guaranteeing receipt by the receiver, but there is a possibility of the receiver receiving duplicate messages; 
            2 indicates the message will be sent successfully and only once, resulting in high resource overhead.
 *      @param {string} options.ssl Optional, SSL configuration class
 */
mqtt.updateConfig = function (options, id) {
    if (!options) {
        throw new Error("dxMqtt.updateConfig: 'options' parameter should not be null or empty")
    }
    if (options.mqttAddr === undefined || options.mqttAddr.length === 0) {
        throw new Error("dxMqtt.updateConfig: 'options.mqttAddr' parameter should not be null or empty")
    }
    if (options.clientId === undefined || options.clientId.length === 0) {
        throw new Error("dxMqtt.updateConfig: 'options.clientId' parameter should not be null or empty")
    }
    if (options.qos === undefined || options.qos == null) {
        throw new Error("dxMqtt.updateConfig: 'options.qos' parameter should not be null or empty")
    }
    let pointer = dxCommon.handleId("mqtt", id)
    let res = mqttObj.setConfig(pointer, options);
    return res;
}

/**
 * Send MQTT request
 * @param {string} topic Subject (required)
 * @param {string} payload Message body content (required)
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 */
mqtt.send = function (topic, payload, id) {
    if (topic === undefined || topic.length === 0) {
        throw new Error("dxMqtt.send:'topic' parameter should not be null or empty")
    }
    if (payload === undefined || payload.length === 0) {
        throw new Error("dxMqtt.send:'payload' parameter should not be null or empty")
    }
    let pointer = dxCommon.handleId("mqtt", id)
    return mqttObj.sendMsg(pointer, topic, payload);
}

/**
 * To receive MQTT data, polling is required.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @return The MQTT request data has the following structure: {topic:'topic', payload:'content'}
 */
mqtt.receive = function (id) {
    let msg = mqttObj.msgReceive(id);
    return JSON.parse(msg);
}

/**
 * To determine if there is new data, you should first check if there is data before calling `receive` to retrieve it.
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns false: Data exists; true: No data exists.
 */
mqtt.msgIsEmpty = function (id) {
    return mqttObj.msgIsEmpty(id);
}

/**
 * Destroy the MQTT instance
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 */
mqtt.destroy = function (id) {
    let pointer = dxCommon.handleId("mqtt", id)
    mqttObj.deinit(pointer);
}

mqtt.RECEIVE_MSG = '__mqtt__MsgReceive'
mqtt.CONNECTED_CHANGED = '__mqtt__Connect_changed'

/**
 * To implement an MQTT client in a simple way, you only need to call this one function.
 * Receiving a message will trigger an event to be sent to dxEventBus. The event subject is mqtt.RECEIVE_MQTT_MSG, and the content is in the format {topic:'',payload:''}.
 * To send messages, simply use the `mqtt.send` method. The data format sent using MQTT is similar to: `{ topic: "sendtopic1", payload: JSON.stringify({ a: i, b: "ssss" }) }`
 * When the MQTT connection state changes, an event is sent to dxEventBus. The subject of the event is mqtt.CONNECTED_CHANGED, and the content is either 'connected' or 'disconnect'.
 * MQTT requires a network connection, so you must ensure that the dxNet component is initialized before using it.
 * @param {object} options MQTT related parameters are required.
 *      @param {string} options.mqttAddr MQTT service address, required, must begin with tcp://, format is tcp://ip:port
 *      @param {string} options.clientId Client ID (required). Please use different client IDs for different devices.
 *      @param {string} options.username Optional field: MQTT username
 *      @param {string} options.password Optional field: MQTT password
 *      @param {string} options.prefix This field is not required and defaults to an empty string. This means that a prefix will be automatically added before the topic.
 *      @param {number} options.qos 0, 1, 2 are optional; the default is 1. 
            0 indicates the message will be sent at most once and then discarded; 
            1 indicates the message will be sent at least once, guaranteeing receipt by the receiver, but there is a possibility of the receiver receiving duplicate messages; 
            2 indicates the message will be sent successfully and only once, resulting in high resource overhead.
 *      @param {string} options.willTopic Optional. This is the subject of the will message. When communication through the broker is interrupted, an MQTT will message will be automatically triggered.
 *      @param {string} options.willMessage Optional. This field contains the will's content. When communication via the broker is interrupted, an MQTT will message will be automatically triggered; this is the content of the will message.
 *      @param {array}  options.subs Optional field; requires you to subscribe to specific topic groups.
 *      @param {string} options.id  Handle ID, optional (a unique ID is required if initializing multiple instances).
 */
mqtt.run = function (options) {
    if (options === undefined || options.length === 0) {
        throw new Error("dxmqtt.run:'options' parameter should not be null or empty")
    }
    if (options.id === undefined || options.id === null || typeof options.id !== 'string') {
        // handle id
        options.id = ""
    }
    let oldfilepre = '/app/code/dxmodules/mqttWorker'
    let content = std.loadFile(oldfilepre + '.js').replace("{{id}}", options.id)
    let newfile = oldfilepre + options.id + '.js'
    std.saveFile(newfile, content)
    let init = map.get("__mqtt__run_init" + options.id)
    if (!init) {//Ensure initialization only once
        map.put("__mqtt__run_init" + options.id, options)
        bus.newWorker(options.id || "__mqtt", newfile)
    }
}
/**
 * Get the current MQTT connection status
 * @param {string} id Handle ID, optional (must be consistent with the ID in init).
 * @returns 'connected' or 'disconnected'
 */
mqtt.getConnected = function (id) {
    if (id == undefined || id == null) {
        id = ""
    }
    return mqtt.isConnected(id) ? "connected" : "disconnected"
}

export default mqtt;
